// Package main
package main

import (
	"flag"
	"fmt"
	"image"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/klippa-app/go-pdfium/enums"
	"github.com/klippa-app/go-pdfium/references"
	"github.com/klippa-app/go-pdfium/requests"
	"github.com/klippa-app/go-pdfium/responses"

	"git.drofa.ru/batushev.v/pdfcrc/pdf"
	"github.com/signintech/pdft"
	gopdf "github.com/signintech/pdft/minigopdf"
	"github.com/sigurn/crc16"
)

const (
	appver = "PDF CRC16 1.1.0"
)

var (
	password       string
	filePath       string
	resolution     uint
	pages          string
	hashes         []string
	differentes    []string
	renderFormat   = "jpeg"
	hashesFilename = "hashes.txt"
	diffFilename   = "hash.diff"
	hashDelimiter  = ","
	table          *crc16.Table
	writeTrimboxes bool
	trimBoxesDir   string
)

func main() {
	flag.StringVar(&filePath, "pdf", "", "Путь к PDF (обязательное поле)")
	flag.StringVar(&pages, "pages", "", "Перечень страниц (например: 3,8,10-15)")
	flag.UintVar(&resolution, "dpi", 72, "Разрешение растеризированных страниц")
	flag.StringVar(&hashesFilename, "out", "hashes.txt", "Имя файла с хешами")
	flag.StringVar(&diffFilename, "diff", "hash.diff", "Имя файла с различиями в хешах")
	flag.StringVar(&hashDelimiter, "delimiter", ",", "Разделитель значений в файле с хешами")
	flag.BoolVar(&writeTrimboxes, "trimbox", false, "Записывать обрезанные страницы по trimbox в формате PNG")
	flag.StringVar(&trimBoxesDir, "trimbox-dir", "trimbox", "Имя директории для записи обрезанных страниц по trimbox в формате PNG")
	version := flag.Bool("v", false, "version")

	flag.Usage = func() {
		fmt.Println(appver)
		flag.PrintDefaults()
		return
	}
	flag.Parse()

	if *version {
		fmt.Println(appver)
		return
	}

	if filePath == "" {
		return
	}
	start := time.Now()

	normalizeRenderFormat()

	err := pdf.LoadPdfium()
	if err != nil {
		errHandler(fmt.Errorf("Не удалось загрузить библиотеку PDFium: %w", newPdfiumError(err)), ExitCodePdfiumError)
		return
	}
	defer pdf.ClosePdfium()

	processPDF()

	// if err := setHashes(); err != nil {
	// 	fmt.Println(err)
	// }

	writeHashes()
	writeDifferentes()

	if len(differentes) > 0 {
		fmt.Println(fmt.Sprintf("Произошли изменения на %d страницах", len(differentes)))
	} else {
		fmt.Println("Отличий не найдено")
	}

	duration := float64(time.Since(start)) / float64(time.Second)
	fmt.Printf("Время выполнения: %.2f сек\n", duration)
}

func processPDF() {
	table = crc16.MakeTable(crc16.CRC16_ARC)

	doc, closeFile, err := openFile(filePath)
	if err != nil {
		log.Fatal(err)
	}
	defer closeFile()

	defer pdf.PdfiumInstance.FPDF_CloseDocument(&requests.FPDF_CloseDocument{Document: doc.Document})

	pageCount, err := pdf.PdfiumInstance.FPDF_GetPageCount(&requests.FPDF_GetPageCount{
		Document: doc.Document,
	})
	if err != nil {
		errHandler(fmt.Errorf("Не удалось получить количество страниц для PDF %s: %w", filePath, newPdfiumError(err)), ExitCodePdfiumError)
		return
	}

	readHashes(pageCount.PageCount)

	pageRange := "first-last"
	if pages != "" {
		pageRange = pages
	}

	parsedPageRange, _, err := pdf.NormalizePageRange(pageCount.PageCount, pageRange)
	if err != nil {
		errHandler(fmt.Errorf("Некорретный диапазон страниц '%s': %w", pageRange, err), ExitCodeInvalidPageRange)
		return
	}

	var renderPages = map[int]requests.Page{}
	splitPages := strings.Split(*parsedPageRange, ",")

	if len(splitPages) > 1 {
		if pages != "" && isNotNumberSlice(splitPages) {
			return
		}
	}

	for _, page := range splitPages {
		pageInt, _ := strconv.Atoi(page)
		pageIndex := pageInt - 1
		renderPages[pageIndex] = requests.Page{
			ByIndex: &requests.PageByIndex{
				Document: doc.Document,
				Index:    pageIndex,
			},
		}
	}

	for i := range hashes {
		renderPage := renderPages[i]
		if renderPage.ByIndex == nil {
			continue
		}
		namePage := strconv.Itoa(renderPage.ByIndex.Index + 1)
		value, err := getCheckSumRenderPagePixels(renderPage)
		if err != nil {
			errHandler(fmt.Errorf("Не получилось отрендерить страницу %s в изображение: %w", namePage, newPdfiumError(err)), ExitCodePdfiumError)

		}
		if value != hashes[i] {
			differentes = append(differentes, fmt.Sprintf("%s: %s -> %s", namePage, hashes[i], value))
		}
		hashes[i] = value
	}

	// fontLoaded := false
	// fontData, err := os.ReadFile("C:\\Windows\\Fonts\\arial.ttf")
	// if err == nil {
	// 	fontLoaded = true
	// }

	// if fontLoaded {
	// 	fontLoading, err := pdf.PdfiumInstance.FPDFText_LoadFont(&requests.FPDFText_LoadFont{
	// 		Document: doc.Document,
	// 		Data:     fontData,
	// 		FontType: enums.FPDF_FONT_TRUETYPE,
	// 	})
	// 	if err == nil {
	// 		for i := range hashes {
	// 			renderPage := renderPages[i]
	// 			if renderPage.ByIndex == nil {
	// 				continue
	// 			}

	// 			txt, err := pdf.PdfiumInstance.FPDFPageObj_CreateTextObj(&requests.FPDFPageObj_CreateTextObj{
	// 				Document: doc.Document,
	// 				Font:     fontLoading.Font,
	// 				FontSize: 12,
	// 			})
	// 			if err != nil {
	// 				continue
	// 			}

	// 			textObject := txt.PageObject

	// 			m := newDefaultMatrix().scale()

	// 			t, err := pdf.PdfiumInstance.FPDFPageObj_Transform(&requests.FPDFPageObj_Transform{
	// 				PageObject: textObject,
	// 				Transform: structs.FPDF_FS_MATRIX{
	// 					A: 0,
	// 					B: 0,
	// 					C: 0,
	// 					D: 0,
	// 					E: 0,
	// 					F: 0,
	// 				},
	// 			})
	// 			if err != nil {
	// 				continue
	// 			}
	// 			fmt.Println(t)

	// 			r, err := pdf.PdfiumInstance.FPDFText_SetText(&requests.FPDFText_SetText{
	// 				PageObject: textObject,
	// 				Text:       hashes[i],
	// 			})

	// 			fmt.Println(r, err)
	// 		}
	// 	}
	// }

	// writeHashes()
	// writeDifferentes()
}

func setHashes() error {
	var pt pdft.PDFt
	if err := pt.Open(filePath); err != nil {
		return err
	}

	// fmt.Println(pt.GetNumberOfPage())

	if err := pt.AddFont("arial", "C:/Windows/Fonts/arial.ttf"); err != nil {
		return err
	}

	if err := pt.SetFont("arial", "", 12); err != nil {
		return err
	}

	for i := range hashes {
		if err := pt.Insert("test", i, 10, 10, 100, 16, gopdf.Center|gopdf.Bottom, nil); err != nil {
			fmt.Printf("Не получилось вставить текст: %v, страница: %d\n", err, i)
		}
	}

	fmt.Println(filePath + ".new.pdf")
	return pt.Save(filePath + ".new.pdf")
}

func getCheckSumRenderToFile(renderPage requests.Page) (value string, err error) {
	renderRequest := &requests.RenderToFile{
		OutputFormat:  requests.RenderToFileOutputFormatJPG,
		MaxFileSize:   0,
		OutputQuality: 100,
		Progressive:   false,
		OutputTarget:  requests.RenderToFileOutputTargetBytes,
	}

	if renderFormat == "png" {
		renderRequest.OutputFormat = requests.RenderToFileOutputFormatPNG
	}

	renderRequest.RenderPageInDPI = &requests.RenderPageInDPI{
		Page: renderPage,
		DPI:  int(resolution),
	}

	result, err := pdf.PdfiumInstance.RenderToFile(renderRequest)
	if err != nil {
		return value, err
	}

	crc := crc16.Checksum(*result.ImageBytes, table)
	return fmt.Sprintf("%04X", crc), nil
}

func getCheckSumRenderPageDPI(renderPage requests.Page) (value string, err error) {
	renderRequest := &requests.RenderPageInDPI{
		Page: renderPage,
		DPI:  int(resolution),
	}

	resultRender, err := pdf.PdfiumInstance.RenderPageInDPI(renderRequest)
	if err != nil {
		return value, err
	}
	result := sByteFromSIntTwo(resultRender.Result.Image.Pix, false)
	crc := crc16.Checksum(result, table)
	return fmt.Sprintf("%04X", crc), nil
}

func getCheckSumRenderPagePixels(renderPage requests.Page) (value string, err error) {
	requestPageSize := &requests.GetPageSize{
		Page: renderPage,
	}

	pageSize, err := pdf.PdfiumInstance.GetPageSize(requestPageSize)
	if err != nil {
		return value, err
	}

	renderRequest := &requests.RenderPageInPixels{
		Page:        renderPage,
		Width:       int(pageSize.Width),
		Height:      int(pageSize.Height),
		RenderFlags: enums.FPDF_RENDER_FLAG_PRINTING,
	}

	hasTrimBox := true
	tb, err := getTrimBox(renderPage)
	if err != nil {
		errHandler(fmt.Errorf("Не получилось получить trimbox страницы: %w", newPdfiumError(err)), ExitCodePdfiumError)
		hasTrimBox = false
	}

	resultRender, err := pdf.PdfiumInstance.RenderPageInPixels(renderRequest)
	if err != nil {
		return value, err
	}

	var result []byte
	if hasTrimBox {
		img := resultRender.Result.Image
		rect := image.Rectangle{
			Min: image.Point{X: int(tb.Left), Y: int(tb.Bottom)},
			Max: image.Point{X: int(tb.Right), Y: int(tb.Top)},
		}
		cropImg := img.SubImage(rect).(*image.RGBA)

		if writeTrimboxes {
			namePage := fmt.Sprintf("%03d", renderPage.ByIndex.Index+1)
			trimBoxesDir := filepath.Join(filepath.Dir(filePath), trimBoxesDir)
			os.MkdirAll(trimBoxesDir, os.ModePerm)
			if f, err := os.Create(filepath.Join(trimBoxesDir, namePage+".png")); err == nil {
				defer f.Close()
				errEncode := png.Encode(f, cropImg)
				if errEncode != nil {
					fmt.Println(fmt.Errorf("Не получилось отрендерить тримбокс в изображение: %w", newPdfiumError(err)))
				}
			}
		}
		result = sByteFromSIntTwo(cropImg.Pix, false)
	} else {
		result = sByteFromSIntTwo(resultRender.Result.Image.Pix, false)
	}

	crc := crc16.Checksum(result, table)
	return fmt.Sprintf("%04X", crc), nil
}

func getTrimBox(renderPage requests.Page) (*responses.FPDFPage_GetTrimBox, error) {
	trimBoxRequest := &requests.FPDFPage_GetTrimBox{
		Page: renderPage,
	}
	return pdf.PdfiumInstance.FPDFPage_GetTrimBox(trimBoxRequest)
}

func closePageFunc(page references.FPDF_PAGE) {
	pdf.PdfiumInstance.FPDF_ClosePage(&requests.FPDF_ClosePage{
		Page: page,
	})
}

func closePageObject(obj references.FPDF_PAGEOBJECT) {
	//
}
